Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 02525cee55a2da8c826d94debd90d150e2f21b31


Parents : a683e2d
Author : Mark Qvist <mark@adepta.io>
Date : 2020-05-27T16:17:40+02:00

Cleaned up whitespace

Changes

2 files changed, 500 insertions(+), 500 deletions(-)

M Libraries/RNode.py +492 -494

Diff

diff --git a/Libraries/Example.py b/Libraries/Example.py
index 24cea49..bc4613e 100644
--- a/Libraries/Example.py
+++ b/Libraries/Example.py
@@ -7,13 +7,15 @@ from RNode import RNodeInterface
# We'll also define which serial port the
# RNode is attached to.
serialPort = "/dev/ttyUSB0"
+# TODO: Remove
+serialPort = "/dev/tty.usbserial-DN03E0FQ"
# This function gets called every time a
# packet is received
def gotPacket(data, rnode):
- print "Received a packet: "+data
- print "RSSI: "+str(rnode.r_stat_rssi)+" dBm"
- print "SNR: "+str(rnode.r_stat_snr)+" dB"
+ print("Received a packet: "+data)
+ print("RSSI: "+str(rnode.r_stat_rssi)+" dBm")
+ print("SNR: "+str(rnode.r_stat_snr)+" dB")
# Create an RNode instance. This configures
# and powers up the radio.
@@ -30,10 +32,10 @@ rnode = RNodeInterface(
# Enter a loop waiting for user input.
try:
- print "Waiting for packets, hit enter to send a packet, Ctrl-C to exit"
+ print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit")
while True:
- raw_input()
+ input()
rnode.send("Hello World!")
except KeyboardInterrupt as e:
- print ""
+ print("")
exit()
\ No newline at end of file

diff --git a/Libraries/RNode.py b/Libraries/RNode.py
index 408661e..3f238b0 100644
--- a/Libraries/RNode.py
+++ b/Libraries/RNode.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
from time import sleep
import sys
import serial
@@ -7,499 +6,498 @@ import time
import math
class KISS():
- FEND = chr(0xC0)
- FESC = chr(0xDB)
- TFEND = chr(0xDC)
- TFESC = chr(0xDD)
-
- CMD_UNKNOWN = chr(0xFE)
- CMD_DATA = chr(0x00)
- CMD_FREQUENCY = chr(0x01)
- CMD_BANDWIDTH = chr(0x02)
- CMD_TXPOWER = chr(0x03)
- CMD_SF = chr(0x04)
- CMD_CR = chr(0x05)
- CMD_RADIO_STATE = chr(0x06)
- CMD_RADIO_LOCK = chr(0x07)
- CMD_DETECT = chr(0x08)
- CMD_PROMISC = chr(0x0E)
- CMD_READY = chr(0x0F)
- CMD_STAT_RX = chr(0x21)
- CMD_STAT_TX = chr(0x22)
- CMD_STAT_RSSI = chr(0x23)
- CMD_STAT_SNR = chr(0x24)
- CMD_BLINK = chr(0x30)
- CMD_RANDOM = chr(0x40)
- CMD_FW_VERSION = chr(0x50)
- CMD_ROM_READ = chr(0x51)
-
- DETECT_REQ = chr(0x73)
- DETECT_RESP = chr(0x46)
-
- RADIO_STATE_OFF = chr(0x00)
- RADIO_STATE_ON = chr(0x01)
- RADIO_STATE_ASK = chr(0xFF)
-
- CMD_ERROR = chr(0x90)
- ERROR_INITRADIO = chr(0x01)
- ERROR_TXFAILED = chr(0x02)
- ERROR_EEPROM_LOCKED = chr(0x03)
-
- @staticmethod
- def escape(data):
- data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd))
- data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc))
- return data
-
+ FEND = 0xC0
+ FESC = 0xDB
+ TFEND = 0xDC
+ TFESC = 0xDD
+ CMD_UNKNOWN = 0xFE
+ CMD_DATA = 0x00
+ CMD_FREQUENCY = 0x01
+ CMD_BANDWIDTH = 0x02
+ CMD_TXPOWER = 0x03
+ CMD_SF = 0x04
+ CMD_CR = 0x05
+ CMD_RADIO_STATE = 0x06
+ CMD_RADIO_LOCK = 0x07
+ CMD_DETECT = 0x08
+ CMD_PROMISC = 0x0E
+ CMD_READY = 0x0F
+ CMD_STAT_RX = 0x21
+ CMD_STAT_TX = 0x22
+ CMD_STAT_RSSI = 0x23
+ CMD_STAT_SNR = 0x24
+ CMD_BLINK = 0x30
+ CMD_RANDOM = 0x40
+ CMD_FW_VERSION = 0x50
+ CMD_ROM_READ = 0x51
+
+ DETECT_REQ = 0x73
+ DETECT_RESP = 0x46
+
+ RADIO_STATE_OFF = 0x00
+ RADIO_STATE_ON = 0x01
+ RADIO_STATE_ASK = 0xFF
+
+ CMD_ERROR = chr0x90
+ ERROR_INITRADIO = chr0x01
+ ERROR_TXFAILED = chr0x02
+ ERROR_EEPROM_LOCKED = chr0x03
+
+ @staticmethod
+ def escape(data):
+ data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
+ data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
+ return data
+
class RNodeInterface():
- MTU = 500
- MAX_CHUNK = 32768
- FREQ_MIN = 137000000
- FREQ_MAX = 1020000000
-
- LOG_CRITICAL = 0
- LOG_ERROR = 1
- LOG_WARNING = 2
- LOG_NOTICE = 3
- LOG_INFO = 4
- LOG_VERBOSE = 5
- LOG_DEBUG = 6
- LOG_EXTREME = 7
-
- RSSI_OFFSET = 157
- SNR_OFFSET = 128
-
- def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = -1, flow_control = True):
- self.serial = None
- self.loglevel = loglevel
- self.callback = callback
- self.name = name
- self.port = port
- self.speed = 115200
- self.databits = 8
- self.parity = serial.PARITY_NONE
- self.stopbits = 1
- self.timeout = 100
- self.online = False
-
- self.frequency = frequency
- self.bandwidth = bandwidth
- self.txpower = txpower
- self.sf = sf
- self.cr = cr
- self.state = KISS.RADIO_STATE_OFF
- self.bitrate = 0
-
- self.r_frequency = None
- self.r_bandwidth = None
- self.r_txpower = None
- self.r_sf = None
- self.r_cr = None
- self.r_state = None
- self.r_lock = None
- self.r_stat_rx = None
- self.r_stat_tx = None
- self.r_stat_rssi = None
- self.r_stat_snr = None
- self.r_random = None
-
- self.packet_queue = []
- self.flow_control = flow_control
- self.interface_ready = False
-
- self.validcfg = True
- if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
- self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR)
- self.validcfg = False
-
- if (self.txpower < 0 or self.txpower > 17):
- self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR)
- self.validcfg = False
-
- if (self.bandwidth < 7800 or self.bandwidth > 500000):
- self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR)
- self.validcfg = False
-
- if (self.sf < 7 or self.sf > 12):
- self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR)
- self.validcfg = False
-
- if (not self.validcfg):
- raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
-
- try:
- self.log("Opening serial port "+self.port+"...")
- self.serial = serial.Serial(
- port = self.port,
- baudrate = self.speed,
- bytesize = self.databits,
- parity = self.parity,
- stopbits = self.stopbits,
- xonxoff = False,
- rtscts = False,
- timeout = 0,
- inter_byte_timeout = None,
- write_timeout = None,
- dsrdtr = False,
- )
- except Exception as e:
- self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR)
- raise e
-
- if self.serial.is_open:
- sleep(2.0)
- thread = threading.Thread(target=self.readLoop)
- thread.setDaemon(True)
- thread.start()
- self.online = True
- self.log("Serial port "+self.port+" is now open")
- self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE)
- self.initRadio()
- if (self.validateRadioState()):
- self.interface_ready = True
- self.log(str(self)+" is configured and powered up")
- sleep(1.0)
- else:
- self.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR)
- self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR)
- self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR)
- self.serial.close()
- raise IOError("RNode interface did not pass validation")
- else:
- raise IOError("Could not open serial port")
-
- def log(self, message, level):
- pass
-
- def initRadio(self):
- self.setFrequency()
- self.setBandwidth()
- self.setTXPower()
- self.setSpreadingFactor()
- self.setRadioState(KISS.RADIO_STATE_ON)
-
- def setFrequency(self):
- c1 = self.frequency >> 24
- c2 = self.frequency >> 16 & 0xFF
- c3 = self.frequency >> 8 & 0xFF
- c4 = self.frequency & 0xFF
- data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4))
-
- kiss_command = KISS.FEND+KISS.CMD_FREQUENCY+data+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring frequency for "+self(str))
-
- def setBandwidth(self):
- c1 = self.bandwidth >> 24
- c2 = self.bandwidth >> 16 & 0xFF
- c3 = self.bandwidth >> 8 & 0xFF
- c4 = self.bandwidth & 0xFF
- data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4))
-
- kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
-
- def setTXPower(self):
- txp = chr(self.txpower)
- kiss_command = KISS.FEND+KISS.CMD_TXPOWER+txp+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring TX power for "+self(str))
-
- def setSpreadingFactor(self):
- sf = chr(self.sf)
- kiss_command = KISS.FEND+KISS.CMD_SF+sf+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
-
- def setCodingRate(self):
- cr = chr(self.cr)
- kiss_command = KISS.FEND+KISS.CMD_CR+cr+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring coding rate for "+self(str))
-
- def setRadioState(self, state):
- kiss_command = KISS.FEND+KISS.CMD_RADIO_STATE+state+KISS.FEND
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring radio state for "+self(str))
-
- def validateRadioState(self):
- self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE)
- sleep(0.25);
- if (self.frequency != self.r_frequency):
- self.log("Frequency mismatch", RNodeInterface.LOG_ERROR)
- self.validcfg = False
- if (self.bandwidth != self.r_bandwidth):
- self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR)
- self.validcfg = False
- if (self.txpower != self.r_txpower):
- self.log("TX power mismatch", RNodeInterface.LOG_ERROR)
- self.validcfg = False
- if (self.sf != self.r_sf):
- self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR)
- self.validcfg = False
-
- if (self.validcfg):
- return True
- else:
- return False
-
- def setPromiscuousMode(self, state):
- if state == True:
- kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x01)+KISS.FEND
- else:
- kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x00)+KISS.FEND
-
- written = self.serial.write(kiss_command)
- if written != len(kiss_command):
- raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str))
-
-
- def updateBitrate(self):
- try:
- self.bitrate = self.r_sf * ( (4.0/self.cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
- self.bitrate_kbps = round(self.bitrate/1000.0, 2)
- self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG)
- except:
- self.bitrate = 0
-
- def processIncoming(self, data):
- self.callback(data, self)
-
- def send(self, data):
- self.processOutgoing(data)
-
- def processOutgoing(self,data):
- if self.online:
- if self.interface_ready:
- if self.flow_control:
- self.interface_ready = False
-
- data = KISS.escape(data)
- frame = chr(0xc0)+chr(0x00)+data+chr(0xc0)
- written = self.serial.write(frame)
- if written != len(frame):
- raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
- else:
- self.queue(data)
-
- def queue(self, data):
- self.packet_queue.append(data)
-
- def process_queue(self):
- if len(self.packet_queue) > 0:
- data = self.packet_queue.pop(0)
- self.interface_ready = True
- self.processOutgoing(data)
- elif len(self.packet_queue) == 0:
- self.interface_ready = True
-
- def readLoop(self):
- try:
- in_frame = False
- escape = False
- command = KISS.CMD_UNKNOWN
- data_buffer = ""
- command_buffer = ""
- last_read_ms = int(time.time()*1000)
-
- while self.serial.is_open:
- if self.serial.in_waiting:
- byte = self.serial.read(1)
-
- last_read_ms = int(time.time()*1000)
-
- if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
- in_frame = False
- self.processIncoming(data_buffer)
- data_buffer = ""
- command_buffer = ""
- elif (byte == KISS.FEND):
- in_frame = True
- command = KISS.CMD_UNKNOWN
- data_buffer = ""
- command_buffer = ""
- elif (in_frame and len(data_buffer) < RNodeInterface.MTU):
- if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
- command = byte
- elif (command == KISS.CMD_DATA):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- data_buffer = data_buffer+byte
- elif (command == KISS.CMD_FREQUENCY):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- command_buffer = command_buffer+byte
- if (len(command_buffer) == 4):
- self.r_frequency = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
- self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG)
- self.updateBitrate()
-
- elif (command == KISS.CMD_BANDWIDTH):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- command_buffer = command_buffer+byte
- if (len(command_buffer) == 4):
- self.r_bandwidth = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
- self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG)
- self.updateBitrate()
-
- elif (command == KISS.CMD_TXPOWER):
- self.r_txpower = ord(byte)
- self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG)
- elif (command == KISS.CMD_SF):
- self.r_sf = ord(byte)
- self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG)
- self.updateBitrate()
- elif (command == KISS.CMD_CR):
- self.r_cr = ord(byte)
- self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG)
- self.updateBitrate()
- elif (command == KISS.CMD_RADIO_STATE):
- self.r_state = ord(byte)
- elif (command == KISS.CMD_RADIO_LOCK):
- self.r_lock = ord(byte)
- elif (command == KISS.CMD_STAT_RX):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- command_buffer = command_buffer+byte
- if (len(command_buffer) == 4):
- self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
-
- elif (command == KISS.CMD_STAT_TX):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- command_buffer = command_buffer+byte
- if (len(command_buffer) == 4):
- self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
-
- elif (command == KISS.CMD_STAT_RSSI):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- self.r_stat_rssi = ord(byte)-self.RSSI_OFFSET
-
- elif (command == KISS.CMD_STAT_SNR):
- if (byte == KISS.FESC):
- escape = True
- else:
- if (escape):
- if (byte == KISS.TFEND):
- byte = KISS.FEND
- if (byte == KISS.TFESC):
- byte = KISS.FESC
- escape = False
- self.r_stat_snr = ord(byte)-self.SNR_OFFSET
-
- elif (command == KISS.CMD_RANDOM):
- self.r_random = ord(byte)
- elif (command == KISS.CMD_ERROR):
- if (byte == KISS.ERROR_INITRADIO):
- self.log(str(self)+" hardware initialisation error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
- elif (byte == KISS.ERROR_INITRADIO):
- self.log(str(self)+" hardware TX error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
- else:
- self.log(str(self)+" hardware error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
- elif (command == KISS.CMD_READY):
- # TODO: add timeout and reset if ready
- # command never arrives
- self.process_queue()
-
- else:
- time_since_last = int(time.time()*1000) - last_read_ms
- if len(data_buffer) > 0 and time_since_last > self.timeout:
- self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG)
- data_buffer = ""
- in_frame = False
- command = KISS.CMD_UNKNOWN
- escape = False
- sleep(0.08)
-
- except Exception as e:
- self.online = False
- self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR)
- self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR)
-
- def log(self, msg, level=3):
- logtimefmt = "%Y-%m-%d %H:%M:%S"
- if self.loglevel >= level:
- timestamp = time.time()
- logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
- print(logstring)
-
- def loglevelname(self, level):
- if (level == RNodeInterface.LOG_CRITICAL):
- return "Critical"
- if (level == RNodeInterface.LOG_ERROR):
- return "Error"
- if (level == RNodeInterface.LOG_WARNING):
- return "Warning"
- if (level == RNodeInterface.LOG_NOTICE):
- return "Notice"
- if (level == RNodeInterface.LOG_INFO):
- return "Info"
- if (level == RNodeInterface.LOG_VERBOSE):
- return "Verbose"
- if (level == RNodeInterface.LOG_DEBUG):
- return "Debug"
- if (level == RNodeInterface.LOG_EXTREME):
- return "Extra"
-
- def hexrep(data, delimit=True):
- delimiter = ":"
- if not delimit:
- delimiter = ""
- hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
- return hexrep
-
- def __str__(self):
- return "RNodeInterface["+self.name+"]"
+ MTU = 500
+ MAX_CHUNK = 32768
+ FREQ_MIN = 137000000
+ FREQ_MAX = 1020000000
+
+ LOG_CRITICAL = 0
+ LOG_ERROR = 1
+ LOG_WARNING = 2
+ LOG_NOTICE = 3
+ LOG_INFO = 4
+ LOG_VERBOSE = 5
+ LOG_DEBUG = 6
+ LOG_EXTREME = 7
+
+ RSSI_OFFSET = 157
+ SNR_OFFSET = 128
+
+ def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = -1, flow_control = True):
+ self.serial = None
+ self.loglevel = loglevel
+ self.callback = callback
+ self.name = name
+ self.port = port
+ self.speed = 115200
+ self.databits = 8
+ self.parity = serial.PARITY_NONE
+ self.stopbits = 1
+ self.timeout = 100
+ self.online = False
+
+ self.frequency = frequency
+ self.bandwidth = bandwidth
+ self.txpower = txpower
+ self.sf = sf
+ self.cr = cr
+ self.state = KISS.RADIO_STATE_OFF
+ self.bitrate = 0
+
+ self.r_frequency = None
+ self.r_bandwidth = None
+ self.r_txpower = None
+ self.r_sf = None
+ self.r_cr = None
+ self.r_state = None
+ self.r_lock = None
+ self.r_stat_rx = None
+ self.r_stat_tx = None
+ self.r_stat_rssi = None
+ self.r_stat_snr = None
+ self.r_random = None
+
+ self.packet_queue = []
+ self.flow_control = flow_control
+ self.interface_ready = False
+
+ self.validcfg = True
+ if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
+ self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+
+ if (self.txpower < 0 or self.txpower > 17):
+ self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+
+ if (self.bandwidth < 7800 or self.bandwidth > 500000):
+ self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+
+ if (self.sf < 7 or self.sf > 12):
+ self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+
+ if (not self.validcfg):
+ raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
+
+ try:
+ self.log("Opening serial port "+self.port+"...")
+ self.serial = serial.Serial(
+ port = self.port,
+ baudrate = self.speed,
+ bytesize = self.databits,
+ parity = self.parity,
+ stopbits = self.stopbits,
+ xonxoff = False,
+ rtscts = False,
+ timeout = 0,
+ inter_byte_timeout = None,
+ write_timeout = None,
+ dsrdtr = False,
+ )
+ except Exception as e:
+ self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR)
+ raise e
+
+ if self.serial.is_open:
+ sleep(2.0)
+ thread = threading.Thread(target=self.readLoop)
+ thread.setDaemon(True)
+ thread.start()
+ self.online = True
+ self.log("Serial port "+self.port+" is now open")
+ self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE)
+ self.initRadio()
+ if (self.validateRadioState()):
+ self.interface_ready = True
+ self.log(str(self)+" is configured and powered up")
+ sleep(1.0)
+ else:
+ self.log("After configuring "+str(self)+", the actual radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR)
+ self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR)
+ self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR)
+ self.serial.close()
+ raise IOError("RNode interface did not pass validation")
+ else:
+ raise IOError("Could not open serial port")
+
+ def log(self, message, level):
+ pass
+
+ def initRadio(self):
+ self.setFrequency()
+ self.setBandwidth()
+ self.setTXPower()
+ self.setSpreadingFactor()
+ self.setRadioState(KISS.RADIO_STATE_ON)
+
+ def setFrequency(self):
+ c1 = self.frequency >> 24
+ c2 = self.frequency >> 16 & 0xFF
+ c3 = self.frequency >> 8 & 0xFF
+ c4 = self.frequency & 0xFF
+ data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4))
+
+ kiss_command = KISS.FEND+KISS.CMD_FREQUENCY+data+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring frequency for "+self(str))
+
+ def setBandwidth(self):
+ c1 = self.bandwidth >> 24
+ c2 = self.bandwidth >> 16 & 0xFF
+ c3 = self.bandwidth >> 8 & 0xFF
+ c4 = self.bandwidth & 0xFF
+ data = KISS.escape(chr(c1)+chr(c2)+chr(c3)+chr(c4))
+
+ kiss_command = KISS.FEND+KISS.CMD_BANDWIDTH+data+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring bandwidth for "+self(str))
+
+ def setTXPower(self):
+ txp = chr(self.txpower)
+ kiss_command = KISS.FEND+KISS.CMD_TXPOWER+txp+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring TX power for "+self(str))
+
+ def setSpreadingFactor(self):
+ sf = chr(self.sf)
+ kiss_command = KISS.FEND+KISS.CMD_SF+sf+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring spreading factor for "+self(str))
+
+ def setCodingRate(self):
+ cr = chr(self.cr)
+ kiss_command = KISS.FEND+KISS.CMD_CR+cr+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring coding rate for "+self(str))
+
+ def setRadioState(self, state):
+ kiss_command = KISS.FEND+KISS.CMD_RADIO_STATE+state+KISS.FEND
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring radio state for "+self(str))
+
+ def validateRadioState(self):
+ self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE)
+ sleep(0.25);
+ if (self.frequency != self.r_frequency):
+ self.log("Frequency mismatch", RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+ if (self.bandwidth != self.r_bandwidth):
+ self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+ if (self.txpower != self.r_txpower):
+ self.log("TX power mismatch", RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+ if (self.sf != self.r_sf):
+ self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR)
+ self.validcfg = False
+
+ if (self.validcfg):
+ return True
+ else:
+ return False
+
+ def setPromiscuousMode(self, state):
+ if state == True:
+ kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x01)+KISS.FEND
+ else:
+ kiss_command = KISS.FEND+KISS.CMD_PROMISC+chr(0x00)+KISS.FEND
+
+ written = self.serial.write(kiss_command)
+ if written != len(kiss_command):
+ raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str))
+
+
+ def updateBitrate(self):
+ try:
+ self.bitrate = self.r_sf * ( (4.0/self.cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
+ self.bitrate_kbps = round(self.bitrate/1000.0, 2)
+ self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG)
+ except:
+ self.bitrate = 0
+
+ def processIncoming(self, data):
+ self.callback(data, self)
+
+ def send(self, data):
+ self.processOutgoing(data)
+
+ def processOutgoing(self,data):
+ if self.online:
+ if self.interface_ready:
+ if self.flow_control:
+ self.interface_ready = False
+
+ data = KISS.escape(data)
+ frame = chr(0xc0)+chr(0x00)+data+chr(0xc0)
+ written = self.serial.write(frame)
+ if written != len(frame):
+ raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
+ else:
+ self.queue(data)
+
+ def queue(self, data):
+ self.packet_queue.append(data)
+
+ def process_queue(self):
+ if len(self.packet_queue) > 0:
+ data = self.packet_queue.pop(0)
+ self.interface_ready = True
+ self.processOutgoing(data)
+ elif len(self.packet_queue) == 0:
+ self.interface_ready = True
+
+ def readLoop(self):
+ try:
+ in_frame = False
+ escape = False
+ command = KISS.CMD_UNKNOWN
+ data_buffer = ""
+ command_buffer = ""
+ last_read_ms = int(time.time()*1000)
+
+ while self.serial.is_open:
+ if self.serial.in_waiting:
+ byte = self.serial.read(1)
+
+ last_read_ms = int(time.time()*1000)
+
+ if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
+ in_frame = False
+ self.processIncoming(data_buffer)
+ data_buffer = ""
+ command_buffer = ""
+ elif (byte == KISS.FEND):
+ in_frame = True
+ command = KISS.CMD_UNKNOWN
+ data_buffer = ""
+ command_buffer = ""
+ elif (in_frame and len(data_buffer) < RNodeInterface.MTU):
+ if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
+ command = byte
+ elif (command == KISS.CMD_DATA):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ data_buffer = data_buffer+byte
+ elif (command == KISS.CMD_FREQUENCY):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ command_buffer = command_buffer+byte
+ if (len(command_buffer) == 4):
+ self.r_frequency = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
+ self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG)
+ self.updateBitrate()
+
+ elif (command == KISS.CMD_BANDWIDTH):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ command_buffer = command_buffer+byte
+ if (len(command_buffer) == 4):
+ self.r_bandwidth = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
+ self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG)
+ self.updateBitrate()
+
+ elif (command == KISS.CMD_TXPOWER):
+ self.r_txpower = ord(byte)
+ self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG)
+ elif (command == KISS.CMD_SF):
+ self.r_sf = ord(byte)
+ self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG)
+ self.updateBitrate()
+ elif (command == KISS.CMD_CR):
+ self.r_cr = ord(byte)
+ self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG)
+ self.updateBitrate()
+ elif (command == KISS.CMD_RADIO_STATE):
+ self.r_state = ord(byte)
+ elif (command == KISS.CMD_RADIO_LOCK):
+ self.r_lock = ord(byte)
+ elif (command == KISS.CMD_STAT_RX):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ command_buffer = command_buffer+byte
+ if (len(command_buffer) == 4):
+ self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
+
+ elif (command == KISS.CMD_STAT_TX):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ command_buffer = command_buffer+byte
+ if (len(command_buffer) == 4):
+ self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3])
+
+ elif (command == KISS.CMD_STAT_RSSI):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ self.r_stat_rssi = ord(byte)-self.RSSI_OFFSET
+
+ elif (command == KISS.CMD_STAT_SNR):
+ if (byte == KISS.FESC):
+ escape = True
+ else:
+ if (escape):
+ if (byte == KISS.TFEND):
+ byte = KISS.FEND
+ if (byte == KISS.TFESC):
+ byte = KISS.FESC
+ escape = False
+ self.r_stat_snr = ord(byte)-self.SNR_OFFSET
+
+ elif (command == KISS.CMD_RANDOM):
+ self.r_random = ord(byte)
+ elif (command == KISS.CMD_ERROR):
+ if (byte == KISS.ERROR_INITRADIO):
+ self.log(str(self)+" hardware initialisation error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+ elif (byte == KISS.ERROR_INITRADIO):
+ self.log(str(self)+" hardware TX error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+ else:
+ self.log(str(self)+" hardware error (code "+self.hexrep(byte)+")", RNodeInterface.LOG_ERROR)
+ elif (command == KISS.CMD_READY):
+ # TODO: add timeout and reset if ready
+ # command never arrives
+ self.process_queue()
+
+ else:
+ time_since_last = int(time.time()*1000) - last_read_ms
+ if len(data_buffer) > 0 and time_since_last > self.timeout:
+ self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG)
+ data_buffer = ""
+ in_frame = False
+ command = KISS.CMD_UNKNOWN
+ escape = False
+ sleep(0.08)
+
+ except Exception as e:
+ self.online = False
+ self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR)
+ self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR)
+
+ def log(self, msg, level=3):
+ logtimefmt = "%Y-%m-%d %H:%M:%S"
+ if self.loglevel >= level:
+ timestamp = time.time()
+ logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg
+ print(logstring)
+
+ def loglevelname(self, level):
+ if (level == RNodeInterface.LOG_CRITICAL):
+ return "Critical"
+ if (level == RNodeInterface.LOG_ERROR):
+ return "Error"
+ if (level == RNodeInterface.LOG_WARNING):
+ return "Warning"
+ if (level == RNodeInterface.LOG_NOTICE):
+ return "Notice"
+ if (level == RNodeInterface.LOG_INFO):
+ return "Info"
+ if (level == RNodeInterface.LOG_VERBOSE):
+ return "Verbose"
+ if (level == RNodeInterface.LOG_DEBUG):
+ return "Debug"
+ if (level == RNodeInterface.LOG_EXTREME):
+ return "Extra"
+
+ def hexrep(data, delimit=True):
+ delimiter = ":"
+ if not delimit:
+ delimiter = ""
+ hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
+ return hexrep
+
+ def __str__(self):
+ return "RNodeInterface["+self.name+"]"


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────